package main

import "fmt"

/*
1、结构体可以使用嵌套匿名结构体所有的字段和方法，即：首字母大写或者小写的字段、方法，都可以使用
2、匿名结构体字段访问可以简化
3、当结构体和匿名结构体有相同的字段或者方法时，编译器采用就近访问原则访问，如希望访问匿名结构体的字段和方法，可以通过匿名结构体名来区分
4、结构体嵌入两个(或多个)匿名结构体，如两个匿名结构体有相同的字段和方法(同时结构体本身没有同名的字段和方法)，在访问时，就必须明确指定匿名结构体名字，否则编译报错
5、如果一个struct嵌套了一个有名结构体，这种模式就是组合，如果是组合关系，那么在访问组合的结构体的字段或方法时，必须带上结构体的名字
6、嵌套匿名结构体后，也可以在创建结构体变量(实例)时，直接指定各个匿名结构体字段的值
7、结构体的匿名字段是基本数据类型，是可以访问的
*/
//1
type A struct {
	Name string
	age  int
}

func (a *A) SayOk() {
	fmt.Println("A SayOk", a.Name)
}
func (a *A) hello() {
	fmt.Println("A hello", a.Name)
}

type B struct {
	A
	Name string
}

func (b *B) SayOk() {
	fmt.Println("B SayOk", b.Name)
}

//4
type C struct {
	A
	B
}

//5
type D struct {
	a A //有名结构体 //组合关系
}

//6
type Goods struct {
	Name  string
	Price float64
}
type Brand struct {
	Name    string
	Address string
}
type TV struct {
	Goods
	Brand
}
type TV2 struct {
	*Goods
	*Brand
}

//7
type Monster struct {
	Name string
	Age  int
}
type E struct {
	Monster
	int
}

func main() {
	//1
	var b B
	b.A.Name = "tom"
	b.A.age = 19
	b.A.SayOk()
	b.A.hello()

	//2
	//上面的写法可以简化
	b.Name = "smith"
	b.age = 20
	b.SayOk()
	b.hello()
	/*
		对上面的代码小结
		(1)当我们直接通过b访问字段或方法时，其执行流程如下 比如b.Name
		(2)编译器会先看b对应的类型有没有Name，如果有，则直接调用B类型的Name字段
		(3)如果没有就去看B中嵌入的匿名结构体A中有没有声明Name字段，如果有就调用，如果没有，继续查找...，如果都找不到就报错
	*/
	//3就近原则，就是优先使用子类的字段和方法
	var b1 B
	b1.Name = "jack"    //B中的Name
	b1.A.Name = "scott" //A中的Name
	b1.age = 100        //A中的age
	b1.SayOk()          //B的方法SayOk
	b1.hello()          //A的方法hello

	//4
	var c C
	//如果c没有Name字段，而A和B有Name，这时就必须通过指定匿名结构体名字来区分
	//所以c.Name就会报编译错误，这个规则对方法也是一样的
	//c.Name = "tom" //error
	c.A.Name = "tom"

	//5
	//如果D中有一个有名结构体，则访问有名结构体的名字，比如d.a.Name
	var d D
	// d.Name //报错
	d.a.Name = "jack"

	//6
	tv := TV{Goods{"电视机001", 5000.99}, Brand{"海尔", "山东"}}
	tv2 := TV{
		Goods{
			Price: 5000.99,
			Name:  "电视机002",
		},
		Brand{
			Name:    "海尔",
			Address: "山东",
		},
	}
	fmt.Println("tv", tv)
	fmt.Println("tv2", tv2)
	tv3 := TV2{&Goods{"电视机003", 7000.99}, &Brand{"创维", "河南"}}
	tv4 := TV2{
		&Goods{
			Name:  "电视机004",
			Price: 9000.99,
		},
		&Brand{
			Name:    "长虹",
			Address: "四川",
		},
	}
	fmt.Println("tv3", *tv3.Goods, *tv3.Brand)
	fmt.Println("tv4", *tv4.Goods, *tv4.Brand)

	//7
	//如果一个结构体有int类型的匿名字段，就不能有第二个
	//如果需要多个int字段，则必须给int字段指定名字
	var e E
	e.Name = "狐狸精"
	e.Age = 300
	e.int = 88
	fmt.Println("e", e)
}
